// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: linchuan

#include "coin2/exchange/binance/api_order/native_rest_async_asio_subscriber.h"

#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>

#include "coin2/base/log.h"

namespace coin2::exchange::binance::api_order {

void BinanceRestAsyncAsioSubscriber::QueryTransfer() {
  RestParams params;
  auto http_context = rest_client()->QueryDeposit(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);

  http_context = rest_client()->QueryWithdrawal(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);

  BinanceNativePrivateClient* client = dynamic_cast<BinanceNativePrivateClient*>(rest_client());
  std::string email = client->GetFirstKeyEmail();  // eg: prestoinvt@gmail.com
  params.set_to_email(email);
  params.set_from_email("");
  http_context = rest_client()->QueryTransfer(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);

  params.set_to_email("");
  params.set_from_email(email);
  http_context = rest_client()->QueryTransfer(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::QueryBalance() {
  if (is_readonly_order_system_ && symcache_->is_margin()) {
    {
      RestParams params;
      params.set_recv_window(5000);
      auto http_context = rest_client()->QueryMarginCrossedAccountBalance(params);
      if (http_context) {
        http_context->handler =
            std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
        http_driver_->PostAsyncContext(http_context);
      }
    }
    {
      RestParams params;
      params.set_recv_window(5000);
      auto http_context = rest_client()->QueryMarginIsolatedAccountBalance(params);
      if (http_context) {
        http_context->handler =
            std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
        http_driver_->PostAsyncContext(http_context);
      }
    }
    return;
  }
  RestParams params;
  params.set_recv_window(5000);
  auto http_context = rest_client()->QueryAccountBalance(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::QueryFlexibleProductPosition() {
  RestParams params;
  params.set_recv_window(5000);
  auto http_context = rest_client()->QueryFlexibleProductPosition(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::QueryLockedProductPosition() {
  RestParams params;
  params.set_recv_window(5000);
  auto http_context = rest_client()->QueryLockedProductPosition(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::QueryOpenOrders(const std::string& native_symbol) {
  RestParams params;
  params.set_symbol(native_symbol);
  params.set_recv_window(5000);
  auto http_context = rest_client()->QueryOpenOrders(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::QueryHistoryOrders(const std::string& native_symbol) {
  RestParams params;
  params.set_symbol(native_symbol);
  params.set_limit(90);
  params.set_recv_window(5000);
  auto http_context = rest_client()->QueryAllOrders(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::QueryFills(const std::string& native_symbol) {
  RestParams params;
  params.set_symbol(native_symbol);
  params.set_limit(90);
  params.set_recv_window(5000);
  auto http_context = rest_client()->QueryTrades(params);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::GenListenKey(bool is_init) {
  auto http_context = rest_client()->GenListenKey(is_init);
  if (!http_context) return;
  http_context->handler =
      std::bind(&BinanceRestAsyncAsioSubscriber::HandleQuery, this, std::placeholders::_1);
  http_driver_->PostAsyncContext(http_context);
}

void BinanceRestAsyncAsioSubscriber::SubscribeAsync() {
  AddPeriodicTask(
      std::bind(&BinanceRestAsyncAsioSubscriber::GenListenKey, this, true),
      5,
      "init_gen_listen_key");

  AddPeriodicTask(
      std::bind(&BinanceRestAsyncAsioSubscriber::GenListenKey, this, false),
      300,
      "gen_listen_key",
      300);

  AddPeriodicTask(
      std::bind(&BinanceRestAsyncAsioSubscriber::QueryBalance, this),
      config_.query_account_balance_period_sec(),
      "query_balance");

  if (is_readonly_order_system_) {
    // query flexible product position and locked product position for both spot and margin
    AddPeriodicTask(
        std::bind(&BinanceRestAsyncAsioSubscriber::QueryFlexibleProductPosition, this),
        config_.query_account_balance_period_sec() * 5,
        "query_flexible_product_position");

    AddPeriodicTask(
        std::bind(&BinanceRestAsyncAsioSubscriber::QueryLockedProductPosition, this),
        config_.query_account_balance_period_sec() * 5,
        "query_locked_product_position");

    if (symcache_->is_spot()) {
      AddPeriodicTask(
          std::bind(&BinanceRestAsyncAsioSubscriber::QueryTransfer, this),
          config_.query_account_balance_period_sec(),
          "query_transfer");
    }
  }

  const auto symbol_list = native_symbols();

  auto query_open_orders_period_sec = config_.query_open_orders_period_sec();
  if (symbol_list.size() < 14) {
    AddPeriodicTask(
        std::bind(&BinanceRestAsyncAsioSubscriber::QueryOpenOrders, this, std::placeholders::_1),
        query_open_orders_period_sec,
        "query_open_orders",
        symbol_list);
  } else {
    if (query_open_orders_period_sec < 60) {
      LOG(WARNING) << "query open orders is too frequent as it consumes 40 rate limit per time";
    }
    AddPeriodicTask(
        std::bind(&BinanceRestAsyncAsioSubscriber::QueryOpenOrders, this, ""),
        query_open_orders_period_sec,
        "query_open_orders");
  }

  AddPeriodicTask(
      std::bind(&BinanceRestAsyncAsioSubscriber::QueryHistoryOrders, this, std::placeholders::_1),
      std::bind(
          &BinanceRestAsyncAsioSubscriber::SkipQuery,
          this,
          "query_history_orders",
          std::placeholders::_1),
      config_.query_cancel_orders_period_sec(),
      "query_history_orders",
      symbol_list);

  AddPeriodicTask(
      std::bind(&BinanceRestAsyncAsioSubscriber::QueryFills, this, std::placeholders::_1),
      std::bind(
          &BinanceRestAsyncAsioSubscriber::SkipQuery,
          this,
          "query_fills",
          std::placeholders::_1),
      config_.query_fill_period_sec(),
      "query_fills",
      symbol_list);
}

}  // namespace coin2::exchange::binance::api_order
